﻿using System;
using System.Collections;
using System.Reflection;
using System.Threading;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Media;
using System.Runtime.InteropServices;
using System.Windows.Media.Imaging;
using System.Windows.Controls;
using System.Windows.Threading;
using System.Windows.Interop;
using Microsoft.Xna.Framework.Graphics;

using Heaven.Mathematics;
using Heaven.Graphics;
using Heaven.Geometry;

using XNA = Microsoft.Xna.Framework.Graphics;
using Color = Heaven.Graphics.Color;
using VertexElement = Heaven.Graphics.VertexElement;
using Point = System.Drawing.Point;
using RenderTarget = Heaven.Graphics.RenderTarget;

namespace Heaven.Xna.Graphics
{
    /// <summary>
    /// Implementation of XNA device that is compatible with 
    /// Windows Presentation Foundation .NET subsystem
    /// </summary>
    public class WindowsPresentationDevice : IDevice
    {
        #region Events

        /// <summary>
        /// Event occures when device is needing to 
        /// render its content (for ex., when 
        /// inner buffer has changed size)
        /// </summary>
        public event EventHandler RenderRequest;

        /// <summary>
        /// Occurs when IsAvailable property has been changed
        /// </summary>
        public event EventHandler AvailabilityChanged;

        #endregion

        #region Fields

        // Static internal device
        //static HwndSource hwnd = null;
        static GraphicsDevice device = null;
        static DepthStencilBuffer deviceDepthBuffer = null;
        // Count of created devices
        static int deviceCount = 0;
        //static Mutex mutex = new Mutex(false);
        //static Monitor monitor = 
        static object locker = new object();
        //static AutoResetEvent locker = new AutoResetEvent(true);
        static Thread currentThread = null;
        
        // Shader compiler
        static IShaderCompiler shaderCompiler = null;
        // Caches device representaions of polygonal objects
        static PolygonalManager polygonalManager = null;
        // Caches device representaions of texture objects
        static TextureManager textureManager = null;
        // Caches shader objects
        static ShaderManager shaderManager = null;

        // Default color
        XNA.Color defaultColor = XNA.Color.White;
        // Default depth value
        double defaultDepth = 1.0f;
        
        // Current setted render target
        RenderTarget currentRenderTarget = null;
        // Is device disposed
        bool disposed = false;


        // Target image
        Image target = null;
        D3DImage d3dimage = null;
        RenderTarget2D frontBuffer = null;
        RenderTarget2D backBuffer = null;
        IntPtr frontBufferPointer = IntPtr.Zero;
        IntPtr backBufferPointer = IntPtr.Zero;
        DepthStencilBuffer depthBuffer = null;

        // Tracer to resize when it is required
        DispatcherTimer resizeTimer = null;
        System.Windows.Size newTargetSize = new System.Windows.Size(2,2);
        bool newTargetSizeValid = false;
        bool needResize = false;
        
        ImageSource blankImage = null;

        // Hack methods
        static MethodInfo copySurfaceMethod = null;
        object[] copySurfaceMethodParameters = null;

        // Is device available?
        bool available = true;
        // If dispose requider during Begin..Present, 
        // we need to dispose after Present
        bool deferredDispose = false;


        bool compositionTargetUpdateRequired = false;
        bool compositionTargetSetBackBufferRequired = false;
        

        #endregion

        #region Свойства

        /// <summary>
        /// Is device available?
        /// </summary>
        public bool IsAvailable 
        {
            get { return available; }
        }

        /// <summary>
        /// Целевой контрол в который помещается 
        /// кадр, когда вызывается Present, а если стоит 
        /// AutoSize, то будет меняться разрешение 
        /// внутреннего буфера при изменении размера контрола
        /// </summary>
        public Image Target
        {
            get { return target; }
            set 
            {
                target.SizeChanged -= new SizeChangedEventHandler(OnTargetSizeChanged);
                target.Source = null;
                target = value;
                target.Source = d3dimage;
                target.SizeChanged += new SizeChangedEventHandler(OnTargetSizeChanged);
                newTargetSizeValid = false;
            }
        }

        void OnTargetSizeChanged(object sender, SizeChangedEventArgs e)
        {
            Lock();
            newTargetSize.Width = Math.Max(1, e.NewSize.Width);
            newTargetSize.Height = Math.Max(1, e.NewSize.Height);
            newTargetSizeValid = true;
            needResize = true;
            resizeTimer.Start();
            Unlock();
        }

        /// <summary>
        /// Default color
        /// </summary>
        public Color DefaultColor 
        {
            get { return Converter.Convert(defaultColor); }
            set { defaultColor = Converter.Convert(value); } 
        }

        /// <summary>
        /// Default depth value
        /// </summary>
        public double DefaultDepth 
        {
            get { return defaultDepth; }
            set { defaultDepth = value; }
        }

        /// <summary>
        /// Current render target
        /// </summary>
        public RenderTarget RenderTarget 
        {
            get 
            {
                return currentRenderTarget;
            }
            set
            {
                if (disposed) return;
                if (currentRenderTarget == null)
                {
                    // Get back to main buffer
                    device.SetRenderTarget(0, null);
                    device.DepthStencilBuffer = deviceDepthBuffer;
                }
                if (value == null)
                {
                    currentRenderTarget = null;
                    // Set render target and depth buffer
                    device.SetRenderTarget(0, backBuffer);
                    device.DepthStencilBuffer = depthBuffer;
                    return;
                }
                else
                {                    
                    // Resolving previous render target
                    if(currentRenderTarget != null)
                    {
                        textureManager.GetRenderTarget(currentRenderTarget).EndRendering();
                        currentRenderTarget.Invalidate();
                    }
                    if (value == null)
                    {
                        currentRenderTarget = null;
                        return;
                    }

                    currentRenderTarget = value;
                    textureManager.GetRenderTarget(currentRenderTarget).BeginRendering();
                }
            } 
        }

        /// <summary>
        /// Shader compiler
        /// </summary>
        public IShaderCompiler ShaderCompiler
        {
            get
            {
                if (shaderCompiler == null)
                {
                    XNA.EffectPool effectpool = new XNA.EffectPool();
                    shaderCompiler = new ShaderCompiler(device, effectpool, textureManager);
                }
                return shaderCompiler;
            }
        }

        /// <summary>
        /// Текущее разрешение внутреннего буфера
        /// </summary>
        public Heaven.Graphics.Size Resolution 
        {
            get { return new Heaven.Graphics.Size(frontBuffer.Width, frontBuffer.Height); }
            set
            {
                newTargetSize = new System.Windows.Size(value.Width, value.Height);
                needResize = true;
                if (RenderRequest != null) RenderRequest(this, EventArgs.Empty);
            }
        }

        #endregion

        #region Initialization

        /// <summary>
        /// Default constructor
        /// </summary>
        public WindowsPresentationDevice(Image target)
        {
            this.target = target;

            Lock();
            try
            {
                
                target.SizeChanged += new SizeChangedEventHandler(OnTargetSizeChanged);

                deviceCount++;
                if (device == null)
                {
                    // Create offscreen window            
                    HwndSource hwnd = new HwndSource(0, 0, 0, 0, 0, "", IntPtr.Zero);
                    // Set parameters
                    XNA.PresentationParameters presentationParameters = new XNA.PresentationParameters();
                    presentationParameters = new XNA.PresentationParameters();
                    presentationParameters.AutoDepthStencilFormat = XNA.DepthFormat.Depth24;
                    presentationParameters.BackBufferCount = 1;
                    presentationParameters.BackBufferFormat = XNA.SurfaceFormat.Color;
                    presentationParameters.BackBufferHeight = 2;
                    presentationParameters.BackBufferWidth = 2;
                    presentationParameters.DeviceWindowHandle = hwnd.Handle;
                    presentationParameters.EnableAutoDepthStencil = true;
                    presentationParameters.IsFullScreen = false;
                    presentationParameters.SwapEffect = XNA.SwapEffect.Discard;
                    presentationParameters.MultiSampleType = XNA.MultiSampleType.None;
                    presentationParameters.MultiSampleQuality = 0;

                    // Create device
                    device = new XNA.GraphicsDevice(
                        XNA.GraphicsAdapter.DefaultAdapter,
                        XNA.DeviceType.Hardware,
                        hwnd.Handle,
                        presentationParameters);

                    // Texture cache
                    textureManager = new TextureManager(device);
                    shaderCompiler = new ShaderCompiler(device, new XNA.EffectPool(), textureManager);
                    shaderManager = new ShaderManager(device);
                    polygonalManager = new PolygonalManager();

                    deviceDepthBuffer = device.DepthStencilBuffer;

                    copySurfaceMethod = device.GetType().GetMethod("CopySurface", BindingFlags.Instance | BindingFlags.NonPublic);
                }

                //device.DeviceLost += new EventHandler(OnDeviceLost);


                resizeTimer = new DispatcherTimer(
                    TimeSpan.FromMilliseconds(250),
                    DispatcherPriority.ApplicationIdle,
                    new EventHandler(OnResizeTimer),
                    Dispatcher.CurrentDispatcher);
                resizeTimer.Start();

                          
                CreateBuffers();
                CompositionTarget.Rendering += new EventHandler(OnCompositionTargetRendering);
            }
            finally
            {
                Unlock();
            }
        }

        void OnDeviceLost(object sender, EventArgs e)
        {
            resizeTimer.Start();
        }

        void Reset()
        {
            // Destroy previous data
            DisposeBuffers();
            device.Dispose();
                                    
            // Set parameters
            XNA.PresentationParameters presentationParameters = new XNA.PresentationParameters();
            presentationParameters = new XNA.PresentationParameters();
            presentationParameters.AutoDepthStencilFormat = XNA.DepthFormat.Depth24;
            presentationParameters.BackBufferCount = 1;
            presentationParameters.BackBufferFormat = XNA.SurfaceFormat.Color;
            presentationParameters.BackBufferHeight = 2;
            presentationParameters.BackBufferWidth = 2;
            presentationParameters.DeviceWindowHandle = device.PresentationParameters.DeviceWindowHandle;
            presentationParameters.EnableAutoDepthStencil = true;
            presentationParameters.IsFullScreen = false;
            presentationParameters.SwapEffect = XNA.SwapEffect.Discard;
            presentationParameters.MultiSampleType = XNA.MultiSampleType.None;
            presentationParameters.MultiSampleQuality = 0;

            // Create device
            device = new XNA.GraphicsDevice(
                XNA.GraphicsAdapter.DefaultAdapter,
                XNA.DeviceType.Hardware,
                presentationParameters.DeviceWindowHandle,
                presentationParameters);

            // Texture cache
            textureManager = new TextureManager(device);
            shaderCompiler = new ShaderCompiler(device, new XNA.EffectPool(), textureManager);
            shaderManager = new ShaderManager(device);
            polygonalManager = new PolygonalManager();

            deviceDepthBuffer = device.DepthStencilBuffer;
        }
        
        void Lock()
        { 
            Monitor.Enter(locker);
        }

        void Unlock()
        {
            Monitor.Exit(locker);
        }

        void OnTargetLayoutUpdated(object sender, EventArgs e)
        {
            target.LayoutUpdated -= new EventHandler(OnTargetLayoutUpdated);
            newTargetSize = new System.Windows.Size(
                    Math.Max(64, (int)target.ActualWidth),
                    Math.Max(64, (int)target.ActualHeight));
            needResize = true;
            newTargetSizeValid = true;
            if (RenderRequest != null) RenderRequest(this, EventArgs.Empty);
        }

        void OnFrontBufferAvailableChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            resizeTimer.Start();
        }

        void OnResizeTimer(object sender, EventArgs e)
        {
            // Skip if the target control has invalid size
            if (!newTargetSizeValid) return;

            resizeTimer.Interval += TimeSpan.FromMilliseconds(400);
            if (resizeTimer.Interval > TimeSpan.FromMilliseconds(1500))
            {
                resizeTimer.Stop();
                resizeTimer.Interval = TimeSpan.FromMilliseconds(250);
            }
            if (RenderRequest != null) RenderRequest(this, EventArgs.Empty);
        }

        void CreateBuffers()
        {
            frontBuffer = new RenderTarget2D(device,
                (int)newTargetSize.Width, (int)newTargetSize.Height, 1, SurfaceFormat.Color,
                MultiSampleType.None,
                0);
            backBuffer = new RenderTarget2D(device,
                (int)newTargetSize.Width, (int)newTargetSize.Height, 1, SurfaceFormat.Color,
                MultiSampleType.None,
                0);
            depthBuffer = new DepthStencilBuffer(
                device, (int)newTargetSize.Width, (int)newTargetSize.Height, DepthFormat.Depth24,
                MultiSampleType.None,
                0);
            device.DepthStencilBuffer = depthBuffer;
            device.SetRenderTarget(0, backBuffer);

            frontBufferPointer = GetNativePointer(frontBuffer);
            backBufferPointer = GetNativePointer(backBuffer);
            copySurfaceMethodParameters = new object[] { backBufferPointer, frontBufferPointer };
            compositionTargetSetBackBufferRequired = true;
        }

        void DisposeBuffers()
        {
            frontBufferPointer = IntPtr.Zero;
            compositionTargetSetBackBufferRequired = true;

            device.SetRenderTarget(0, null);
            device.DepthStencilBuffer = deviceDepthBuffer;

            depthBuffer.Dispose();
            frontBuffer.Dispose();
            backBuffer.Dispose();
        }

        unsafe IntPtr GetNativePointer(Microsoft.Xna.Framework.Graphics.RenderTarget renderTarget)
        {
            object pointer = renderTarget.GetType().GetField("pComPtr", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(renderTarget);
            return new IntPtr(Pointer.Unbox(pointer));
        }


        #endregion

        #region Shading

        /// <summary>
        /// Finds the parameter by semantic name
        /// </summary>
        /// <param name="shader">Shader</param>
        /// <param name="semantic">Semantic</param>
        /// <returns>Parameter name or null if the parameter is not found</returns>
        public string GetParameterBySemantic(Shader shader, string semantic)
        {
            Effect effect = shaderManager.Get(shader);
            EffectParameter parameter = effect.Parameters.GetParameterBySemantic(semantic);
            return parameter == null ? null : parameter.Name;
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, bool value)
        {
            Effect effect = shaderManager.Get(shader);
            EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            parameter.SetValue(value);
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, bool[] value)
        {
            Effect effect = shaderManager.Get(shader);
            EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            parameter.SetValue(value);
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, float value)
        {
            Effect effect = shaderManager.Get(shader);
            EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            parameter.SetValue((float)value);
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, float[] value)
        {
            Effect effect = shaderManager.Get(shader);
            EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            float[] copy = new float[value.Length];
            for (int i = 0; i < value.Length; i++) copy[i] = (float)value[i];
            parameter.SetValue(copy);
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, int value)
        {
            Effect effect = shaderManager.Get(shader);
            EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            parameter.SetValue(value);
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, int[] value)
        {
            Effect effect = shaderManager.Get(shader);
            EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            parameter.SetValue(value);
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, Heaven.Texturing.Texture value)
        {
            Effect effect = shaderManager.Get(shader);
            EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            parameter.SetValue(textureManager.Get(value));
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, Color value)
        {
            Effect effect = shaderManager.Get(shader);
            EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            parameter.SetValue(Converter.ConvertToVector4(value));
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, Color[] value)
        {
            Effect effect = shaderManager.Get(shader);
            EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            Microsoft.Xna.Framework.Vector4[] colors = new Microsoft.Xna.Framework.Vector4[value.Length];
            for (int i = 0; i < value.Length; i++)
            {
                colors[i] = Converter.ConvertToVector4(value[i]);
            }
            parameter.SetValue(colors);
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, Matrix4 value)
        {
            Effect effect = shaderManager.Get(shader);
            EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            parameter.SetValue(Converter.Convert(value));
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, Matrix4[] value)
        {
            Effect effect = shaderManager.Get(shader);
            EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            Microsoft.Xna.Framework.Matrix[] matrices = new Microsoft.Xna.Framework.Matrix[value.Length];
            for (int i = 0; i < value.Length; i++)
            {
                matrices[i] = Converter.Convert(value[i]);
            }
            parameter.SetValue(matrices);
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, Vector4 value)
        {
            Effect effect = shaderManager.Get(shader);
            EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            parameter.SetValue(Converter.Convert(value));
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, Vector3 value)
        {
            Effect effect = shaderManager.Get(shader);
            EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            parameter.SetValue(Converter.Convert(value));
        }

        /// <summary>
        /// Sets value to the given parameter of a shader
        /// </summary>
        /// <param name="shader">A shader</param>
        /// <param name="name">Name of a parameter</param>
        /// <param name="value">Value</param>
        public void SetShaderParameter(Shader shader, string name, Vector4[] value)
        {
            Effect effect = shaderManager.Get(shader);
            EffectParameter parameter = effect.Parameters[name];
            if (parameter == null) return;
            Microsoft.Xna.Framework.Vector4[] vecs = new Microsoft.Xna.Framework.Vector4[value.Length];
            for (int i = 0; i < value.Length; i++)
            {
                vecs[i] = Converter.Convert(value[i]);
            }
            parameter.SetValue(vecs);
        }

        #endregion

        #region Rendering

        /// <summary>
        /// Begins rendering
        /// </summary>
        public void Begin()
        {
            Lock();
            if (disposed) return;

            if (frontBuffer.GraphicsDevice.IsDisposed)
            {
                DisposeBuffers();
                CreateBuffers();
            }

            //device.SetRenderTarget(0, null);
            device.SetRenderTarget(0, backBuffer);
            device.DepthStencilBuffer = depthBuffer;
        }

        /// <summary>
        /// Ends of rendering process, swaps front and back buffers
        /// </summary>
        public void Present()
        {
            bool unlockCancel = false;
            try
            {
                if (!newTargetSizeValid) return;
                if (disposed) return;
                if (currentRenderTarget != null)
                {
                    throw new Exception("There is no way to perform Present operation. You have to end redering to the render target");
                }

                if (device.GraphicsDeviceStatus != GraphicsDeviceStatus.Normal)
                {
                    while (device.GraphicsDeviceStatus == GraphicsDeviceStatus.Lost)
                    {
                        Thread.Sleep(1);
                    }
                    
                    Reset();
                    resizeTimer.Start();
                    return;
                }

                // Do resize
                if (needResize)
                {
                    needResize = false;

                    RenderTarget2D backupFrontBuffer = frontBuffer;
                    RenderTarget2D backupBackBuffer = backBuffer;
                    DepthStencilBuffer backupDepthBuffer = depthBuffer;

                    /*device.SetRenderTarget(0, null);
                    device.DepthStencilBuffer = deviceDepthBuffer;*/

                    CreateBuffers();
                                       

                    /*device.SetRenderTarget(0, backBuffer);
                    device.DepthStencilBuffer = depthBuffer;*/
                    //compositionTargetSetBackBufferRequired = true;

                    /*Thread.Sleep(100);
                    device.SetRenderTarget(0, null);
                    Clear();
                    device.Present();


                    device.SetRenderTarget(0, backBuffer);

                    

                    Clear();*/

                    unlockCancel = true;
                    Unlock();
                    
                    //if (RenderRequest != null) RenderRequest(this, EventArgs.Empty);
                    resizeTimer.Start();

                    backupDepthBuffer.Dispose();
                    backupFrontBuffer.Dispose();
                    backupBackBuffer.Dispose();
                    
                    return;
                }

                // Save the render target content in the other surface
                copySurfaceMethod.Invoke(device, copySurfaceMethodParameters);
                compositionTargetUpdateRequired = true;
                frameCount++;
            }
            finally
            {
                // Allow other cooperative device rendering
                if (!unlockCancel) Unlock();
                // Defered disposing
                if (deferredDispose) Destruct();
            }
        }

        void OnCompositionTargetRendering(object sender, EventArgs e)
        {
            if (d3dimage == null)
            {
                blankImage = new BitmapImage(new Uri(@"pack://application:,,,/Heaven.Xna;component/Resources/Images/ImageBlank.png"));
                d3dimage = new D3DImage();
                d3dimage.IsFrontBufferAvailableChanged += new DependencyPropertyChangedEventHandler(OnFrontBufferAvailableChanged);
                newTargetSize = new System.Windows.Size(1, 1);
                target.Source = d3dimage;
                target.LayoutUpdated += new EventHandler(OnTargetLayoutUpdated);                
            }
            /*if (compositionTargetSetBackBufferRequired)
            {
                System.Diagnostics.Debug.WriteLine("CT: compositionTargetSetBackBufferRequired");

                d3dimage.Lock();
                d3dimage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, frontBufferPointer);
                d3dimage.Unlock();

                compositionTargetSetBackBufferRequired = false;
            }*/
            if (compositionTargetUpdateRequired)
            {
                if (disposed) return;
                try
                {
                    if (d3dimage.IsFrontBufferAvailable && device.GraphicsDeviceStatus == GraphicsDeviceStatus.Normal)
                    {
                        d3dimage.Lock();
                        if (compositionTargetSetBackBufferRequired)
                        {
                            System.Diagnostics.Debug.WriteLine("CT: compositionTargetSetBackBufferRequired");
                            d3dimage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, frontBufferPointer);
                            compositionTargetSetBackBufferRequired = false;
                        }
                        if (frontBufferPointer != IntPtr.Zero) d3dimage.AddDirtyRect(new Int32Rect(0, 0, d3dimage.PixelWidth, d3dimage.PixelHeight));
                        d3dimage.Unlock();
                        compositionTargetUpdateRequired = false;
                    }
                }
                catch (Exception exception)
                {
                    System.Diagnostics.Debug.WriteLine("Exception during back buffer settting: " + exception.Message);
                }
                
            }
        }

        /// <summary>
        /// Clears depth and frame
        /// </summary>
        public void Clear()
        {
            device.Clear(XNA.ClearOptions.Target | XNA.ClearOptions.DepthBuffer, defaultColor, (float)defaultDepth, 0);
        }

        /// <summary>
        /// Clears depth
        /// </summary>
        public void ClearDepth()
        {
            device.Clear(XNA.ClearOptions.DepthBuffer, defaultColor, (float)defaultDepth, 0);
        }

        /// <summary>
        /// Clears frame
        /// </summary>
        public void ClearFrame()
        {
            device.Clear(XNA.ClearOptions.Target, defaultColor, (float)defaultDepth, 0);
        }

        /// <summary>
        /// Draws a polygonal object
        /// </summary>
        /// <param name="polygonal">Polygonal object</param>
        /// <param name="shader">Shader object</param>
        public void Draw(Polygonal polygonal, Shader shader)
        {
            if (polygonal.IsEmpty) return;
            Effect effect = shaderManager.Get(shader);
            EffectTechnique technique = effect.CurrentTechnique; 
            effect.Begin();
            try
            {
                for (int i = 0; i < effect.CurrentTechnique.Passes.Count; i++)
                {
                    technique.Passes[i].Begin();
                    polygonalManager.Draw(device, polygonal);
                    technique.Passes[i].End();
                }
            }
            finally
            {
                effect.End();
            }
        }

        void SafeInvoke(ThreadStart method)
        {
            if (Thread.CurrentThread != target.Dispatcher.Thread)
            {
                target.Dispatcher.Invoke((ParameterizedThreadStart)SafeInvokeHelper, DispatcherPriority.Render, method);
            }
            else
            {
                try
                {
                    method();
                }
                catch (Exception exception)
                {
                    System.Diagnostics.Debug.WriteLine("Exception in XNA Windows Presentation Device, message: " +
                        exception.InnerException.Message);
                }
            }
        }

        void SafeInvokeHelper(object method)
        {
            try
            {
                ((ThreadStart)method).Invoke();
            }
            catch (Exception exception)
            {
                System.Diagnostics.Debug.WriteLine("Exception in XNA Windows Presentation Device during Dispatcher invoke, message: " +
                    exception.InnerException.Message);
            }
        }

        #endregion

        #region Destructor

        /// <summary>
        /// Destructor
        /// </summary>
        ~WindowsPresentationDevice()
        {
            Destruct();
        }

        /// <summary>
        /// Forces disposing this device immediately 
        /// </summary>
        public void Dispose()
        {
            GC.SuppressFinalize(this);
            Destruct();
        }

        // Disposes unmanaged resources
        void Destruct()
        {
            if (!disposed)
            {
                /*if (!Monitor.TryEnter(1))
                {
                    // During Begin..Present we
                    // need to defer disposing
                    deferredDispose = true;
                    return;
                }*/
                try
                {
                    Lock();

                    //CompositionTarget.Rendering -= OnCompositionTargetRendering;

                    disposed = true;
                    available = false;

                    if (AvailabilityChanged != null) AvailabilityChanged(this, EventArgs.Empty);

                    if(resizeTimer != null) resizeTimer.Stop();

                    try
                    {
                        device.SetRenderTarget(0, null);
                        depthBuffer.Dispose();
                        frontBuffer.Dispose();
                        backBuffer.Dispose();
                    }
                    catch { };

                    deviceCount--;
                    if (deviceCount == 0)
                    {
                        if(device != null) device.Dispose();
                        //SafeInvoke(delegate { hwnd.Dispose(); });
                        polygonalManager = null;
                        textureManager = null;
                        shaderManager = null;
                        device = null;
                    }
                }
                finally
                {
                    Unlock();
                }
            }
        }

        #endregion

        #region Debug

        static int timeStamp = 0;
        static int frameCount = 0;

        /// <summary>
        /// Shows monitor with statistics
        /// </summary>
        public static void ShowMonitor()
        {
            DispatcherTimer timer = new DispatcherTimer();
            timer.Interval = TimeSpan.FromSeconds(0.6);
            timer.Tick += new EventHandler(OnMonitorUpdate);
            timer.Start();
        }

        static void OnMonitorUpdate(object sender, EventArgs e)
        {
            // Calculate FPS
            int newTimeStamp = Environment.TickCount;
            int timeDelta = newTimeStamp - timeStamp;
            int fps = timeDelta == 0 ? 0:(int)((((double)frameCount) / (double)(timeDelta)) * 1000.0);
            timeStamp = newTimeStamp;
            frameCount = 0;

            string text = String.Format("Polygonals: {0}\nTextures: {1}\nRender targets: {2}\nDevices: {3}\nManaged memory: {4:0.#}Mb\nMemory allocated: {5:0.#}Mb\nWorking set: {6:0.#}Mb\nFrame per second: {7:0}",
                polygonalManager != null ? polygonalManager.Count : 0,
                textureManager != null ? textureManager.CountOfTextures : 0,
                textureManager != null ? textureManager.CountOfRenderTargets : 0,
                deviceCount,
                (double)((double)GC.GetTotalMemory(false) / (1024.0*1024.0)),
                (double)((double)System.Diagnostics.Process.GetCurrentProcess().PrivateMemorySize64 / (1024.0 * 1024.0)),
                (double)((double)System.Diagnostics.Process.GetCurrentProcess().WorkingSet64 / (1024.0 * 1024.0)),
                fps);
            Debug.DebugMonitor.Show(text);
        }

        #endregion
    }
}
